package nl.altenpts.net.kad;

import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import nl.altenpts.net.NetReactor;

/**
 * 
 * Operation is the base class to operations that require. Operation maintains the administration 
 * for number of outstanding requests, short time-outs, etc. 
 * 
 * @author fabian
 *
 */

public abstract class Operation {
	
	public Operation(List<Node> nodes, Id target, NetReactor reactor, Callback cb) {
		
		this.target   = target;
		this.reactor  = reactor;
		this.comp = new DistanceComparator(target);
		this.callback = cb;
		
		this.nodes = nodes;
		Collections.sort(this.nodes, comp);
		Iterator<Node> iterator = nodes.iterator();
		while(iterator.hasNext()) {
			nodestates.put(iterator.next(), STATE_UNREQUESTED);
		}
	}

	
	/**
	 * Starts the operation. This operation should be called only once.
	 */
	public void go() {
		sendRequests();
	}
		
	private synchronized void sendRequests() {
		for(Node n = nextNode(); numshortoutstanding < Kademlia.a && n != null; n = nextNode()) {
			sendRequest(n);
			numshortoutstanding++;
			numoutstanding++;
			reactor.addTask(new ShortTimeoutHandler(n), new Date().getTime() + SHORT_TIMEOUT, false);
			nodestates.put(n, STATE_REQUESTING);
		}
		
		if(numoutstanding == 0) {
			sendRequest(null);
		}
	}
	
	private Node nextNode() {
		
		Iterator<Node> iterator = nodes.iterator();
		int numnodes = 0;
		
		while(numnodes < Kademlia.k && iterator.hasNext()) {
			Node n = iterator.next();
			switch(nodestates.get(n)) {
			case STATE_UNREQUESTED:
				numnodes++;
				return n;
			case STATE_REQUESTING:
			case STATE_ANSWERED:
				numnodes++;
				break;
			}
		}
				
		return null;	
	}
	
	
	/**
	 * Must be called by subclass if the subclass wishes to add
	 * a "requestable" node.
	 * @param n
	 */
	protected synchronized void addNodes(List<Node> newnodes) {
		Iterator<Node> iterator = newnodes.iterator();
		while(iterator.hasNext()) {
			Node n = iterator.next();
			int index = Collections.binarySearch(this.nodes, n, comp);
			if(index < 0) {
				index = -index - 1; 
				this.nodes.add(index, n);
				nodestates.put(n, STATE_UNREQUESTED);
			}			
		}
		sendRequests();
	}
	
	/**
	 * Must be called by subclass if Node n responded or timed-out  
	 * 
	 * @param n The Node that responded
	 * @param state must be STATE_ANSWERED or STATE_TIMEDOUT
	 */
	protected synchronized void nodeResponded(Node n, int state) {
		assert(state == STATE_ANSWERED || state == STATE_TIMEDOUT);
		if(nodestates.get(n) == STATE_REQUESTING) {
			numshortoutstanding--;
		}
		nodestates.put(n, state);
		numoutstanding--;
		sendRequests();
	}
	
	protected void respond(Object response) {
		if(callback != null) {
			callback.onResponse(response);
		}
	}
	
	protected void finish() {
		if(callback != null) {
			callback.onFinished();
		}
	}
	
	protected abstract void sendRequest(Node node);
	
	
	
	private class ShortTimeoutHandler implements Runnable {
		
		public ShortTimeoutHandler(Node n) {
			this.node = n;
		}

		public void run() {
			synchronized(Operation.this) {
				if(nodestates.get(node) == STATE_REQUESTING) {
					numshortoutstanding--;
					nodestates.put(node, STATE_TIMEDOUTSHORT);				
				}			
			}
		}
		private Node node;
	}
	
	protected HashMap<Node, Integer> nodestates = new HashMap<Node, Integer>();
	protected List<Node> nodes; 
	protected Id target;
	protected NetReactor reactor;
	protected Callback callback;
	
	private int numoutstanding = 0;
	private int numshortoutstanding = 0;
	private DistanceComparator comp;
	
	private final static int SHORT_TIMEOUT = 3000;
	
	protected final static int STATE_UNREQUESTED   = 1;
	protected final static int STATE_REQUESTING    = 2;
	protected final static int STATE_ANSWERED      = 3;
	protected final static int STATE_TIMEDOUT      = 4;
	protected final static int STATE_TIMEDOUTSHORT = 5;
		
}
